package main

import (
	"bytes"
	"fmt"
	"github.com/dgraph-io/badger"
	abcitypes "github.com/tendermint/tendermint/abci/types"
	"github.com/tendermint/tendermint/libs/common"
)

// 自定义得Application 定义 存储
type KVStoreApplication struct {
	db           *badger.DB
	currentBatch *badger.Txn

}

// 验证 KVStoreApplication 是否是 Application
var _ abcitypes.Application = (*KVStoreApplication)(nil)

func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
	return &KVStoreApplication{
		db: db,
	}
}


// 版本信息返回
func (KVStoreApplication) Info(req abcitypes.RequestInfo) abcitypes.ResponseInfo {

	return abcitypes.ResponseInfo{

	}
}


// Set non-consensus critical application specific options.
func (KVStoreApplication) SetOption(req  abcitypes.RequestSetOption) abcitypes.ResponseSetOption {

	return abcitypes.ResponseSetOption{}
}


// 加载一次创世区块
// Usage:
// 加载genesis 的时候调用
// 如果ResponseInitChain.Validators 不设置，那么使用配置文件里面的验证人; 如果不为空，那么初始的验证人将更新
// If ResponseInitChain.Validators is not empty, the initial validator set will be the ResponseInitChain.Validators (regardless of what is in RequestInitChain.Validators).
// This allows the app to decide if it wants to accept the initial validator set proposed by tendermint (ie. in the genesis file), or if it wants to use a different one (perhaps computed based on some application specific information in the genesis file).
func (KVStoreApplication) InitChain(req abcitypes.RequestInitChain) abcitypes.ResponseInitChain {


	return abcitypes.ResponseInitChain{}
}




//当Tendermint Core确定了块之后，它被分为3个部分转移到应用程序：
// BeginBlock，每个交易一个DeliverTx，最后一个EndBlock。
// DeliverTx正在异步传输，但预计回应将按顺序进行。
// Header.Time : 上一个区块的时间； Height>1: 是完成共识的时间 。 Height=1 : 创世区块的时间
// VoteInfo[] 签名结果。 验证人 对应的签名结果
func (app *KVStoreApplication) BeginBlock(req abcitypes.RequestBeginBlock) abcitypes.ResponseBeginBlock {
	app.currentBatch = app.db.NewTransaction(true)

	//return abcitypes.ResponseBeginBlock{}

	// 测试 Event
	return abcitypes.ResponseBeginBlock{Events:[]abcitypes.Event{
		{
			Type:                 "beginblock",
			Attributes:           common.KVPairs{
				common.KVPair{
					Key:                  []byte("address"),
					Value:                req.Hash,

				},
			},

		},
	}}
}





// 我们创建一批来存储块的交易

// 注意我们不能在DeliverTx中提交交易，因为在这种情况下，
// 可以并行调用的Query将返回不一致的数据（即使实际的块尚未提交，它也会报告某些值已经存在）。
// GasWanted (int64): Amount of gas requested for transaction.
// ResponseDeliverTx.Code == 0 only if the transaction is fully valid.
func (app *KVStoreApplication) DeliverTx(req abcitypes.RequestDeliverTx) abcitypes.ResponseDeliverTx {
	//code := app.isValid(req.Tx) // 其实这里可以不用再次验证了，因为 TMCore 已经验证通过
	var code uint32
	code=0
	if code != 0 {
		return abcitypes.ResponseDeliverTx{Code: code}
	}

	parts := bytes.Split(req.Tx, []byte("="))
	key, value := parts[0], parts[1]

	// 自定义存储
	err := app.currentBatch.Set(key, value)
	if err != nil {
		panic(err)
	}


	return abcitypes.ResponseDeliverTx{Code: 0}
}


func (KVStoreApplication) EndBlock(req abcitypes.RequestEndBlock) abcitypes.ResponseEndBlock {
	return abcitypes.ResponseEndBlock{}
}



// Commit 指示应用程序保持新状态。
// 同一个高度 Commit 不能调用多次
// Tendermint expects LastBlockAppHash and LastBlockHeight to be updated during Commit,
// ensuring that Commit is never called twice for the same block height
//  ResponseCommit.Data 将会出现在下一个区块头; 区块的hash = AppHash =AppState
// 返回构建hash 格式是什么？
func (app *KVStoreApplication) Commit() abcitypes.ResponseCommit {
	app.currentBatch.Commit() // 批量提交业务数据的交易
	// Data ([]byte): The Merkle root hash of the application state
	// 返回最终的Merkle root hash 状态
	return abcitypes.ResponseCommit{Data: []byte{}}


}


//
// RequestQuery
// Data: []byte 类型即可
// Path: 自定义查询 路径  如  /height ,  /account/ 根据路径在做业务
// Height: 默认是0 ； 可以设置从最新高度

// ResponseQuery
// Codespace (string): Namespace for the Code.
func (app *KVStoreApplication) Query(reqQuery abcitypes.RequestQuery) (resQuery abcitypes.ResponseQuery) {
	resQuery.Key = reqQuery.Data

	fmt.Printf("=====reqQuery.Height:%d, Path:%s,Prove:%v",reqQuery.Height,reqQuery.Path,reqQuery.Prove)
	err := app.db.View(func(txn *badger.Txn) error {
		item, err := txn.Get(reqQuery.Data)


		if err != nil && err != badger.ErrKeyNotFound {
			return err
		}
		if err == badger.ErrKeyNotFound {
			resQuery.Log = "does not exist"
		} else {

			return item.Value(func(val []byte) error {
				resQuery.Log = "exists"
				resQuery.Value = val

				return nil
			})
		}
		return nil
	})
	if err != nil {
		panic(err)
	}
	return
}


// Tx ([]byte): The request transaction bytes
// 当一个新的交易被添加到Tendermint Core时，它会要求应用程序检查它（验证格式，签名、账户余额等）
// Type:
//    CheckTx_New : DeliverTx 调用
//    CheckTx_Recheck :  mempool 检查

// CheckTx need not execute the transaction in full: 不需要在DeliverTx中再次调用
func (app *KVStoreApplication) CheckTx(req abcitypes.RequestCheckTx) abcitypes.ResponseCheckTx {
	code:=app.isValid(req.Tx)
	return abcitypes.ResponseCheckTx{Code: code, GasWanted:1}
}

/*


如果交易格式错误或者key=value已经存在，我们再次得出非0代码。不然，我们将其添加到当前批次。

在当前设计中，块可以包括不正确的交易（通过CheckTx但是没有通过DeliverTx或者提议者直接包括的交易）。
这是出于性能原因而做出的设计。

注意我们不能在DeliverTx中提交交易，因为在这种情况下，可以并行调用的Query将返回不一致的数据
（即使实际的块尚未提交，它也会报告某些值已经存在）。
*/
func (app *KVStoreApplication) isValid(tx []byte) (code uint32) {
	// 这里验证业务规则为：
	//如果交易没有{bytes} = {bytes}的形式，我们返回代码1。
	// 当相同的key =value已经存在（相同的key和value）时，我们返回代码2。
	// 对于其他，我们返回0代码来表明它们是有效的。

	//curl -s 'localhost:26657/broadcast_tx_commit?tx="tendermint=rocks"'
	// tx数据就是 = 》 tendermint=rocks
	// check format
	parts := bytes.Split(tx, []byte("="))
	if len(parts) != 2 {
		return 1
	}

	key, value := parts[0], parts[1]

	// check if the same key=value already exists
	err := app.db.View(func(txn *badger.Txn) error {
		item, err := txn.Get(key)
		if err != nil && err != badger.ErrKeyNotFound {
			return err
		}
		if err == nil {
			return item.Value(func(val []byte) error {
				if bytes.Equal(val, value) {
					code = 2
				}
				return nil
			})
		}
		return nil
	})
	if err != nil {
		panic(err)
	}

	return code
}
